package com.msh.artascope.devcloud.service.service.impl;


import com.msh.artascope.devcloud.client.enums.biz.ModuleRunOperationEnum;
import com.msh.artascope.devcloud.client.enums.biz.ModuleRunningTypeEnum;
import com.msh.artascope.devcloud.client.po.EnvPo;
import com.msh.artascope.devcloud.client.po.ModuleBuildPo;
import com.msh.artascope.devcloud.client.po.ModulePo;
import com.msh.artascope.devcloud.client.po.ModuleRunPo;
import com.msh.artascope.devcloud.client.po.ModuleRunningPo;
import com.msh.artascope.devcloud.client.qo.ModuleRunQo;
import com.msh.artascope.devcloud.client.qo.ModuleRunningQo;
import com.msh.artascope.devcloud.service.dao.ModuleRunDao;
import com.msh.artascope.devcloud.service.model.query.ModuleRunQueryVo;
import com.msh.artascope.devcloud.service.model.vo.ModuleRunVo;
import com.msh.artascope.devcloud.service.model.vo.PodVo;
import com.msh.artascope.devcloud.service.service.EnvService;
import com.msh.artascope.devcloud.service.service.HealthTestService;
import com.msh.artascope.devcloud.service.service.KubernetesConfigService;
import com.msh.artascope.devcloud.service.service.ModuleBuildService;
import com.msh.artascope.devcloud.service.service.ModuleNetworkService;
import com.msh.artascope.devcloud.service.service.ModuleResourceService;
import com.msh.artascope.devcloud.service.service.ModuleReverseProxyService;
import com.msh.artascope.devcloud.service.service.ModuleRunService;
import com.msh.artascope.devcloud.service.service.ModuleRunningService;
import com.msh.artascope.devcloud.service.service.ModuleService;
import com.msh.artascope.devcloud.service.service.ProductService;
import com.msh.artascope.devcloud.service.service.ProjectService;
import com.msh.artascope.devcloud.service.service.RegisterConfigService;
import com.msh.artascope.devcloud.service.wrapper.K8sWrapper;
import com.msh.artascope.devcloud.service.wrapper.K8sWrapper.K8sInfo;
import com.msh.artascope.interfaces.ILoginAccount;
import com.msh.frame.client.base.IConvert;
import com.msh.frame.client.base.SortBy;
import com.msh.frame.client.base.TenantBaseServiceImpl;
import com.msh.frame.client.common.Result;
import com.msh.frame.client.define.ColumnDef;
import com.msh.frame.client.enums.SortModeEnum;
import com.msh.frame.client.enums.YesNoEnum;
import com.msh.frame.client.exception.BizException;
import com.msh.frame.interfaces.IdGenerateable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

/**
 * @author shihu
 * @mail m-sh@qq.com
 */
@Service
@Slf4j
public class ModuleRunServiceImpl extends TenantBaseServiceImpl<ModuleRunPo,ModuleRunQo> implements ModuleRunService {

  @Resource
  private ModuleRunDao moduleRunDao;

  @Resource
  private IdGenerateable idGenerateable;
  @Autowired
  private K8sWrapper k8sWrapper;
  @Autowired
  private ModuleService moduleService;
  @Autowired
  private ProjectService projectService;
  @Autowired
  private ProductService productService;
  @Autowired
  private ModuleBuildService moduleBuildService;
  @Autowired
  private ModuleResourceService moduleResourceService;
  @Autowired
  private HealthTestService healthTestService;
  @Autowired
  private EnvService envService;

  @Resource
  private KubernetesConfigService kubernetesConfigService;
  @Resource
  private RegisterConfigService registerConfigService;

  @Resource
  private ModuleRunningService moduleRunningService;
  @Resource
  private ModuleReverseProxyService moduleReverseProxyService;
  @Resource
  private ModuleNetworkService moduleNetworkService;

  @Override
  public long insert(ModuleRunPo param) {
    param.setId(idGenerateable.getUniqueId());
    return super.insert(param);
  }

  @Override
  public int insertCollection(Collection<ModuleRunPo> param) {
    if (CollectionUtils.isEmpty(param)) {
      return 0;
    }
    Long[] uniqueIds = idGenerateable.getUniqueIds(param.size());
    int i=0;
    for (ModuleRunPo po : param) {
      if(null == po.getId()){
        po.setId(uniqueIds[i++]);
      }
    }
    return super.insertCollection(param);
  }


// ----------前端vo处理-------------------------------------------------------

  private static final String LOG_COMMAND_FORMAT = "kubectl logs -f --tail 100 %s -n %s --container app";
  private static final String CONSOLE_COMMAND_FORMAT = "kubectl exec -it %s -n %s --container app -- bash";
  @Override
  public List<PodVo> listPod(Long moduleId, Long tenantId) {
    ModulePo modulePo = moduleService.get(moduleId, tenantId);
    BizException.doThrowIfNull("未查询到模块信息", modulePo);
    EnvPo envPo = envService.get(modulePo.getEnvId(), tenantId);
    BizException.doThrowIfNull("未查询到环境信息", envPo);
    K8sInfo k8s = kubernetesConfigService.getK8s(envPo, modulePo.getTenantId());
    BizException.doThrowIfNull("缺少k8s配置信息", k8s);
    List<PodVo> podVoList = IConvert.convertList(k8sWrapper.getPods(k8s, modulePo), PodVo.class);
    podVoList.forEach(vo -> {
      vo.setLogCommand(String.format(LOG_COMMAND_FORMAT, vo.getName(), envPo.getNamespace()));
      vo.setConsoleCommand(
          String.format(CONSOLE_COMMAND_FORMAT, vo.getName(), envPo.getNamespace()));
    });
    podVoList.sort((o1, o2) -> {
      int i = o2.getDeploymentName().compareTo(o1.getDeploymentName());
      if (0 != i) {
        return i;
      }
      return o2.getStartTime().compareTo(o1.getStartTime());
    });
    return podVoList;
  }

  @Override
  public String getLogs(Long moduleId, String podName, Integer line, Long tenantId) {
    ModulePo modulePo = moduleService.get(moduleId, tenantId);
    BizException.doThrowIfNull("未查询到模块信息", modulePo);
    K8sInfo k8s = kubernetesConfigService.getK8s(modulePo.getEnvId(), modulePo.getTenantId());
    return k8sWrapper.getPodLogs(k8s, podName, line);
  }

  @Override
  @Transactional
  public ModuleRunVo start(Long buildId, ILoginAccount loginAccount) {
    Long tenantId = loginAccount.getTenantId();
    ModuleBuildPo moduleBuildPo = moduleBuildService.get(buildId, tenantId);

    BizException.doThrowIfNull("缺少构建信息", moduleBuildPo);
    BizException.doThrowIfTrue("当前构建版本已经在运行",
        YesNoEnum.YES.getValue().equals(moduleBuildPo.getRunningFlag()));
    ModulePo modulePo = moduleService.get(moduleBuildPo.getModuleId(), tenantId);
    K8sInfo k8s = kubernetesConfigService.getK8s(modulePo.getEnvId(), modulePo.getTenantId());
    BizException.doThrowIfNull("缺少k8s配置信息", k8s);

    ModuleRunVo moduleRunVo = new ModuleRunVo();
    moduleRunVo.initLoginAccount(loginAccount);
    moduleRunVo.setProjectId(modulePo.getProjectId());
    moduleRunVo.setModuleId(modulePo.getId());
    moduleRunVo.setModuleBuildId(buildId);
    moduleRunVo.setOperation(ModuleRunOperationEnum.START.getCode());
    this.insert(moduleRunVo);

    List<ModuleRunningPo> moduleRunningPoList = moduleRunningService.list(
        new ModuleRunningQo().setModuleId(modulePo.getId()).setTenantId(tenantId));
    moduleRunningPoList.forEach(r -> {
      moduleRunningService.deleteWithDeployment(r, k8s, tenantId);
    });
    moduleRunningService.createWithDeployment(modulePo, moduleBuildPo, k8s,
        ModuleRunningTypeEnum.RELEASE.getCode(), null, null, tenantId);
    // 只要有灰度版本， 就重新构建vs 并删除 dr
    if (moduleRunningPoList.stream()
        .anyMatch(r -> r.getRunningType().equals(ModuleRunningTypeEnum.GRAY.getCode()))) {
      // 重载 反向代理的 istio-vs
      moduleReverseProxyService.reloadVirtualServiceByModuleId(modulePo.getId(), k8s, tenantId);
      // 重载 service的 istio-vs
      moduleNetworkService.reloadVirtualServiceByModuleId(modulePo, k8s, tenantId);
      // 删除 istio-dr
      k8sWrapper.deleteIstioDestinationRule(k8s, modulePo.getName());
    }

    return moduleRunVo;
  }


  @Override
  @Transactional
  public ModuleRunVo restart(Long moduleId, ILoginAccount loginAccount) {
    //获取模块最后一次运行操作
    ModuleRunPo moduleRunPo = this.listOne(new ModuleRunQo().setModuleId(moduleId)
        .setSortBy(new SortBy(ColumnDef.ID, SortModeEnum.DESC)));
    BizException.doThrowIfTrue("当前模块从未启动过", null == moduleRunPo);
    BizException.doThrowIfTrue("越权操作",
        !moduleRunPo.getTenantId().equals(loginAccount.getTenantId()));
    if(moduleRunPo.getOperation().equals(ModuleRunOperationEnum.RESTART.getCode())
        || moduleRunPo.getOperation().equals(ModuleRunOperationEnum.START.getCode())
    ){
      long last = 60000L - (System.currentTimeMillis() - moduleRunPo.getCreateTime().getTime());
      BizException.doThrowIfTrue("启动或重启时间不得少于1分钟, "+ last/1000 +"s 后可重启", last > 0);
    }
    BizException.doThrowIfTrue("停止状态无法重启", moduleRunPo.getOperation().equals(ModuleRunOperationEnum.STOP.getCode()));
    ModulePo modulePo = moduleService.get(moduleId, loginAccount.getTenantId());
    K8sInfo k8s = kubernetesConfigService.getK8s(modulePo.getEnvId(), modulePo.getTenantId());
    BizException.doThrowIfNull("缺少k8s配置信息", k8s);
    modulePo.initLoginAccount(loginAccount);
    ModuleRunVo moduleRunVo = new ModuleRunVo();
    moduleRunVo.initLoginAccount(loginAccount);
    moduleRunVo.setProjectId(modulePo.getProjectId());
    moduleRunVo.setModuleId(modulePo.getId());
    moduleRunVo.setModuleBuildId(moduleRunPo.getModuleBuildId());
    moduleRunVo.setOperation(ModuleRunOperationEnum.RESTART.getCode());
    this.insert(moduleRunVo);
    k8sWrapper.deletePods(k8s, modulePo);
    return moduleRunVo;
  }

  @Override
  @Transactional
  public ModuleRunVo stop(Long moduleId, ILoginAccount loginAccount) {
    Long tenantId = loginAccount.getTenantId();
    //获取模块最后一次运行操作
    ModuleRunPo moduleRunPo = this.listOne(new ModuleRunQo().setModuleId(moduleId)
        .setSortBy(new SortBy(ColumnDef.ID, SortModeEnum.DESC)));
    BizException.doThrowIfTrue("当前模块从未启动过", null == moduleRunPo);
    BizException.doThrowIfTrue("越权操作", !moduleRunPo.getTenantId().equals(tenantId));
    BizException.doThrowIfTrue("当前模块已为停止状态",
        moduleRunPo.getOperation().equals(ModuleRunOperationEnum.STOP.getCode()));
    ModulePo modulePo = moduleService.get(moduleId, tenantId);
    K8sInfo k8s = kubernetesConfigService.getK8s(modulePo.getEnvId(), modulePo.getTenantId());
    BizException.doThrowIfNull("缺少k8s配置信息", k8s);
    ModuleRunVo moduleRunVo = new ModuleRunVo();
    moduleRunVo.initLoginAccount(loginAccount);
    moduleRunVo.setProjectId(modulePo.getProjectId());
    moduleRunVo.setModuleId(modulePo.getId());
    moduleRunVo.setModuleBuildId(moduleRunPo.getModuleBuildId());
    moduleRunVo.setOperation(ModuleRunOperationEnum.STOP.getCode());
    this.insert(moduleRunVo);
    List<ModuleRunningPo> moduleRunningPoList = moduleRunningService.list(
        new ModuleRunningQo().setModuleId(modulePo.getId()).setTenantId(tenantId));
    moduleRunningPoList.forEach(r -> {
      moduleRunningService.deleteWithDeployment(r, k8s, tenantId);
    });
    // 只要有灰度版本， 就重新构建vs 并删除 dr
    if (moduleRunningPoList.stream()
        .anyMatch(r -> r.getRunningType().equals(ModuleRunningTypeEnum.GRAY.getCode()))) {
      // 重载 反向代理的 istio-vs
      moduleReverseProxyService.reloadVirtualServiceByModuleId(modulePo.getId(), k8s, tenantId);
      // 重载 service的 istio-vs
      moduleNetworkService.reloadVirtualServiceByModuleId(modulePo, k8s, tenantId);
      // 删除 istio-dr
      k8sWrapper.deleteIstioDestinationRule(k8s, modulePo.getName());
    }

    return moduleRunVo;
  }


  private ModuleRunVo toVo(ModuleRunPo po){
    ModuleRunVo vo = po.convert(ModuleRunVo.class);
    return vo;
  }

  private List<ModuleRunVo> toVoList(List<ModuleRunPo> list){
    if(CollectionUtils.isEmpty(list)){
      return Collections.emptyList();
    }
    Long tenantId = list.get(0).getTenantId();
    List<ModuleRunVo> voList = IConvert.convertList(list, ModuleRunVo.class);
    Set<Long> buildIdSet = voList.stream().map(ModuleRunVo::getModuleBuildId)
        .collect(Collectors.toSet());
    Map<Long, ModuleBuildPo> map = moduleBuildService.map(buildIdSet, tenantId);
    voList.stream().forEach(vo -> {
      ModuleBuildPo moduleBuildPo = map.get(vo.getModuleBuildId());
      if(null != moduleBuildPo){
        vo.setImageName(moduleBuildPo.getImageName());
      }
    });

    return voList;
  }

  @Override
  public ModuleRunVo getVo(Long id, Long tenantId) {
    ModuleRunPo po = this.get(id, tenantId);
    if(null == po){
      return null;
    }
    ModuleRunVo vo = toVo(po);

    return vo;
  }

  @Override
  public List<ModuleRunVo> listVo(ModuleRunQueryVo q){
    ModuleRunQo qo = q.convert(ModuleRunQo.class);
    List<ModuleRunPo> poList = this.list(qo);
    List<ModuleRunVo> voList = toVoList(poList);

    return voList;
  }

  @Override
  public Result<List<ModuleRunVo>> pageVo(ModuleRunQueryVo param) {
    ModuleRunQo qo = param.convert(ModuleRunQo.class);
    int count = this.count(qo);
    if (0 == count) {
      return Result.successReturn(Collections.<ModuleRunVo>emptyList()).setCount(count);
    }
    List<ModuleRunPo> poList = this.list(qo);
    List<ModuleRunVo> voList = toVoList(poList);

    return Result.successReturn(voList).setCount(count);
  }


}
